From f70da06fd180418867f04caa25833b1cc8f667ad Mon Sep 17 00:00:00 2001 From: robertl Date: Wed, 25 Jun 2008 03:58:21 +0000 Subject: [PATCH] Begin sketching in garmin_device_xml layer to provide transparency for the ...garmin -[io] usb: case. Initially stubbed out. --- gpsbabel/garmin.c | 25 +- gpsbabel/garmin_device_xml.c | 137 +++ gpsbabel/garmin_device_xml.h | 1771 ++++++++++++++++++++++++++++++++++ gpsbabel/jeeps/gpslibusb.c | 24 +- 4 files changed, 1949 insertions(+), 8 deletions(-) create mode 100644 gpsbabel/garmin_device_xml.c create mode 100644 gpsbabel/garmin_device_xml.h diff --git a/gpsbabel/garmin.c b/gpsbabel/garmin.c index 0655ba025..997db4577 100644 --- a/gpsbabel/garmin.c +++ b/gpsbabel/garmin.c @@ -26,6 +26,7 @@ #include "jeeps/gps.h" #include "garmin_tables.h" #include "garmin_fs.h" +#include "garmin_device_xml.h" #define SOON 1 @@ -47,6 +48,8 @@ static char *category = NULL; static char *categorybitsopt = NULL; static int categorybits; +static ff_vecs_t *gpx_vec; + #define MILITANT_VALID_WAYPT_CHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" /* Technically, even this is a little loose as spaces arent allowed */ @@ -75,6 +78,7 @@ arglist_t garmin_args[] = { static const char * d103_symbol_from_icon_number(unsigned int n); static int d103_icon_number_from_symbol(const char *s); + static void rw_init(const char *fname) { @@ -242,8 +246,20 @@ rw_init(const char *fname) if (receiver_charset) cet_convert_init(receiver_charset, 1); +} - +static void +rd_init(const char *fname) +{ + if (setjmp(gdx_jmp_buf)) { + char *vec_opts = NULL; + gdx_info *gi = gdx_get_info(); + gpx_vec = find_vec("gpx", &vec_opts); + gpx_vec->rd_init(gi->from_device.canon); + } else { + gpx_vec = NULL; + rw_init(fname); + } } static void @@ -655,6 +671,11 @@ pvt_read(posn_status *posn_status) static void data_read(void) { + if (gpx_vec) { + gpx_vec->read(); + return; + } + if (poweroff) { return; } @@ -1011,7 +1032,7 @@ data_write(void) ff_vecs_t garmin_vecs = { ff_type_serial, FF_CAP_RW_ALL, - rw_init, + rd_init, rw_init, rw_deinit, rw_deinit, diff --git a/gpsbabel/garmin_device_xml.c b/gpsbabel/garmin_device_xml.c new file mode 100644 index 000000000..268670f69 --- /dev/null +++ b/gpsbabel/garmin_device_xml.c @@ -0,0 +1,137 @@ +/* + Parse 'GarminDevice.xml' on a Garmin mass storage device (e.g. Zumo, + Nuvi, Colorado, etc. and return key device info. + + Copyright (C) 2008 Robert Lipe, robertlipe@gpsbabel.org + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA + + */ + +// References: +// http://developer.garmin.com/web-device/garmin-mass-storage-mode-devices/ +// http://developer.garmin.com/schemas/device/v2/ + +#include "defs.h" +#include "xmlgeneric.h" +#include "garmin_device_xml.h" + +#define MYNAME "whatever" + +static gdx_info *my_gdx_info; +static int type; +static char *mountpoint, *base, *path, *ext; +static xg_callback device_s, id_s, path_s, ext_s, base_s, dir_s; + +void type_s(const char *args, const char **unused) { + type = strcmp(args, "GPSData"); +} + +void device_s(const char *args, const char **unused) { + if (my_gdx_info) { + fatal(MYNAME ": More than one device type found in file.\n"); + } + my_gdx_info = xcalloc(sizeof *my_gdx_info, 1); + my_gdx_info->device_desc = xstrdup(args); +} + +void id_s(const char *args, const char **unused) { + my_gdx_info->device_id = xstrdup(args); +} + +void path_s(const char *args, const char **unused) { + path = xstrdup(args); +} + +void ext_s(const char *args, const char **unused) { + ext = xstrdup(args); +} + +void base_s(const char *args, const char **unused) { + base = xstrdup(args); +} + +void dir_s(const char *args, const char **unused) { + if (type) + return; + if (0 == strcmp(args, "OutputFromUnit")) { + xasprintf(&my_gdx_info->from_device.path, "%s%c%s", + mountpoint, GB_PATHSEP, path); + my_gdx_info->from_device.basename = xstrdup(base); + my_gdx_info->from_device.extension = xstrdup(ext); + xasprintf(&my_gdx_info->from_device.canon, "%s.%s", + my_gdx_info->from_device.path, + my_gdx_info->from_device.extension); + } else + if (0 == strcmp(args, "InputToUnit")) { + xasprintf(&my_gdx_info->to_device.path, "%s%c%s", + mountpoint, GB_PATHSEP, path); + my_gdx_info->to_device.basename = xstrdup(base); + my_gdx_info->to_device.extension = xstrdup(ext); + } else + fatal(MYNAME ":Unknown direction '%s'\n", args); + + if (base) xfree(base) ; + base = NULL; + + if (ext) xfree(ext) ; + ext = NULL; + + if (path) xfree(path) ; + path = NULL; +} + +static xg_tag_mapping gdx_map[] = { + { device_s, cb_cdata, "/Device/Model/Description" }, + { id_s, cb_cdata, "/Device/Id" }, + { path_s, cb_cdata, "/Device/MassStorageMode/DataType/File/Location/Path" }, + { type_s, cb_cdata, "/Device/MassStorageMode/DataType/Name" }, + { ext_s, cb_cdata, "/Device/MassStorageMode/DataType/File/Location/FileExtension" }, + { base_s, cb_cdata, "/Device/MassStorageMode/DataType/File/Location/BaseName" }, + { dir_s, cb_cdata, "/Device/MassStorageMode/DataType/File/TransferDirection" }, +}; + +const gdx_info * +gdx_read(const char *fname) { + xml_init(fname, gdx_map, NULL); + xml_read(); + xml_deinit(); + + return my_gdx_info; +} + + +// Look for the Device in the incoming NULL-terminated list of directories +const gdx_info * +gdx_find_file(char **dirlist) { + const gdx_info *gdx; + while (*dirlist) { + char *tbuf; + xasprintf(&tbuf, "%s/%s", dirlist, "GarminDevice.xml"); + mountpoint = dirlist; + gdx = gdx_read(tbuf); + xfree(tbuf); + if (gdx) { + longjmp(gdx_jmp_buf, 1); + } + dirlist++; + } + return NULL; +} + +const gdx_info * +gdx_get_info() { + return my_gdx_info; +} diff --git a/gpsbabel/garmin_device_xml.h b/gpsbabel/garmin_device_xml.h new file mode 100644 index 000000000..d8b039247 --- /dev/null +++ b/gpsbabel/garmin_device_xml.h @@ -0,0 +1,1771 @@ +/* + Parse 'GarminDevice.xml' on a Garmin mass storage device (e.g. Zumo, + Nuvi, Colorado, etc. and return key device info. + + Copyright (C) 2008 Robert Lipe, robertlipe@gpsbabel.org + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA + + */ + +/* + * Describes a file on the unit. + */ +typedef struct { + const char *path; + const char *basename; + const char *extension; + const char *canon; // full name, when applicable. +} gdx_file; + +/* + * The interesting traits of this device. + */ +typedef struct { + const char *device_desc; + const char *device_id; + const char *device_mounted_path; // Not from the file; about the file. + gdx_file from_device; + gdx_file to_device; +// gdx_file geocache_logs; +} gdx_info; + +const gdx_info* gdx_read(const char *fname); + +// This is so gross. By the time we know it's not a USB device +// and could be one of our devices, we're so deep into the callstack +// that can't back out tracefully without bludgeoning most of the +// (Mac|Lin|Win) x (USB|Serial) matrix. Since we don't *really* want +// to progress any further, we just longjump back to the caller... +#include +jmp_buf gdx_jmp_buf; + +#if 0 + +/* + * The file-level information. + */ +static +struct gpx_global { + gpx_global_entry name; + gpx_global_entry desc; + gpx_global_entry author; + gpx_global_entry email; + gpx_global_entry url; + gpx_global_entry urlname; + gpx_global_entry keywords; + /* time and bounds aren't here; they're recomputed. */ +} *gpx_global ; + +static void +gpx_add_to_global(gpx_global_entry *ge, char *cdata) +{ + queue *elem, *tmp; + gpx_global_entry * gep; + + QUEUE_FOR_EACH(&ge->queue, elem, tmp) { + gep = BASE_STRUCT(elem, gpx_global_entry, queue); + if (0 == strcmp(cdata, gep->tagdata)) + return; + } + + gep = xcalloc(sizeof(*gep), 1); + QUEUE_INIT(&gep->queue); + gep->tagdata = xstrdup(cdata); + ENQUEUE_TAIL(&ge->queue, &gep->queue); +} + +static void +gpx_rm_from_global(gpx_global_entry *ge) +{ + queue *elem, *tmp; + + QUEUE_FOR_EACH(&ge->queue, elem, tmp) { + gpx_global_entry *g = (gpx_global_entry *) dequeue(elem); + xfree(g->tagdata); + xfree(g); + } +} + +static void +gpx_write_gdata(gpx_global_entry *ge, char *tag) +{ + queue *elem, *tmp; + gpx_global_entry * gep; + + if (!gpx_global || QUEUE_EMPTY(&ge->queue)) { + return; + } + + gbfprintf(ofd, "<%s>", tag); + QUEUE_FOR_EACH(&ge->queue, elem, tmp) { + gep = BASE_STRUCT(elem, gpx_global_entry, queue); + gbfprintf(ofd, "%s", gep->tagdata); + /* Some tags we just output once. */ + if ((0 == strcmp(tag, "url")) || + (0 == strcmp(tag, "email"))) { + break; + } + gbfprintf(ofd, " "); + } + gbfprintf(ofd, "\n", tag); +} + + +typedef struct tag_mapping { + tag_type tag_type; /* enum from above for this tag */ + int tag_passthrough; /* true if we don't generate this */ + const char *tag_name; /* xpath-ish tag name */ + unsigned long crc; /* Crc32 of tag_name */ +} tag_mapping; + +/* + * xpath(ish) mappings between full tag paths and internal identifers. + * These appear in the order they appear in the GPX specification. + * If it's not a tag we explictly handle, it doesn't go here. + */ + +tag_mapping tag_path_map[] = { + { tt_gpx, 0, "/gpx", 0UL }, + { tt_name, 0, "/gpx/name", 0UL }, + { tt_desc, 0, "/gpx/desc", 0UL }, + { tt_author, 0, "/gpx/author", 0UL }, + { tt_email, 0, "/gpx/email", 0UL }, + { tt_url, 0, "/gpx/url", 0UL }, + { tt_urlname, 0, "/gpx/urlname", 0UL }, + { tt_keywords, 0, "/gpx/keywords", 0UL }, + + { tt_wpt, 0, "/gpx/wpt", 0UL }, + { tt_wpt_ele, 0, "/gpx/wpt/ele", 0UL }, + { tt_wpt_time, 0, "/gpx/wpt/time", 0UL }, + { tt_wpt_name, 0, "/gpx/wpt/name", 0UL }, + { tt_wpt_cmt, 0, "/gpx/wpt/cmt", 0UL }, + { tt_wpt_desc, 0, "/gpx/wpt/desc", 0UL }, + { tt_wpt_url, 0, "/gpx/wpt/url", 0UL }, + { tt_wpt_urlname, 0, "/gpx/wpt/urlname", 0UL }, + { tt_wpt_link, 0, "/gpx/wpt/link", 0UL }, /* GPX 1.1 */ + { tt_wpt_link_text, 0, "/gpx/wpt/link/text", 0UL }, /* GPX 1.1 */ + { tt_wpt_sym, 0, "/gpx/wpt/sym", 0UL }, + { tt_wpt_type, 1, "/gpx/wpt/type", 0UL }, + + /* Double up the GPX 1.0 and GPX 1.1 styles */ +#define GEOTAG(type,name) \ + {type, 1, "/gpx/wpt/groundspeak:cache/groundspeak:" name, 0UL }, \ + {type, 1, "/gpx/wpt/extensions/cache/" name, 0UL }, \ + {type, 1, "/gpx/wpt/geocache/" name, 0UL } /* opencaching.de */ + +#define GARMIN_WPT_EXT "/gpx/wpt/extensions/gpxx:WaypointExtension" + +// GEOTAG( tt_cache, "cache"), + { tt_cache, 1, "/gpx/wpt/groundspeak:cache" }, + + GEOTAG( tt_cache_name, "name"), + GEOTAG( tt_cache_container, "container"), + GEOTAG( tt_cache_type, "type"), + GEOTAG( tt_cache_difficulty, "difficulty"), + GEOTAG( tt_cache_terrain, "terrain"), + GEOTAG( tt_cache_hint, "encoded_hints"), + GEOTAG( tt_cache_hint, "hints"), /* opencaching.de */ + GEOTAG( tt_cache_desc_short, "short_description"), + GEOTAG( tt_cache_desc_long, "long_description"), + GEOTAG( tt_cache_placer, "owner"), + { tt_cache_log_wpt, 1, "/gpx/wpt/groundspeak:cache/groundspeak:logs/groundspeak:log/groundspeak:log_wpt"}, + { tt_cache_log_wpt, 1, "/gpx/wpt/extensions/cache/logs/log/log_wpt"}, + { tt_cache_log_type, 1, "/gpx/wpt/groundspeak:cache/groundspeak:logs/groundspeak:log/groundspeak:type"}, + { tt_cache_log_type, 1, "/gpx/wpt/extensions/cache/logs/log/type"}, + { tt_cache_log_date, 1, "/gpx/wpt/groundspeak:cache/groundspeak:logs/groundspeak:log/groundspeak:date"}, + { tt_cache_log_date, 1, "/gpx/wpt/extensions/cache/logs/log/date"}, + + { tt_wpt_extensions, 0, "/gpx/wpt/extensions", 0UL }, + + { tt_garmin_wpt_extensions, 0, GARMIN_WPT_EXT, 0UL }, + { tt_garmin_wpt_proximity, 0, GARMIN_WPT_EXT "/gpxx:Proximity", 0UL }, + { tt_garmin_wpt_temperature, 0, GARMIN_WPT_EXT "/gpxx:Temperature", 0UL }, + { tt_garmin_wpt_depth, 0, GARMIN_WPT_EXT "/gpxx:Depth", 0UL }, + { tt_garmin_wpt_display_mode, 0, GARMIN_WPT_EXT "/gpxx:DisplayMode", 0UL }, + { tt_garmin_wpt_categories, 0, GARMIN_WPT_EXT "/gpxx:Categories", 0UL }, + { tt_garmin_wpt_category, 0, GARMIN_WPT_EXT "/gpxx:Categories/gpxx:Category", 0UL }, + { tt_garmin_wpt_addr, 0, GARMIN_WPT_EXT "/gpxx:Address/gpxx:StreetAddress", 0UL }, + { tt_garmin_wpt_city, 0, GARMIN_WPT_EXT "/gpxx:Address/gpxx:City", 0UL }, + { tt_garmin_wpt_state, 0, GARMIN_WPT_EXT "/gpxx:Address/gpxx:State", 0UL }, + { tt_garmin_wpt_country, 0, GARMIN_WPT_EXT "/gpxx:Address/gpxx:Country", 0UL }, + { tt_garmin_wpt_postal_code, 0, GARMIN_WPT_EXT "/gpxx:Address/gpxx:PostalCode", 0UL }, + { tt_garmin_wpt_phone_nr, 0, GARMIN_WPT_EXT "/gpxx:PhoneNumber", 0UL }, + + { tt_rte, 0, "/gpx/rte", 0UL }, + { tt_rte_name, 0, "/gpx/rte/name", 0UL }, + { tt_rte_desc, 0, "/gpx/rte/desc", 0UL }, + { tt_rte_number, 0, "/gpx/rte/number", 0UL }, + { tt_rte_rtept, 0, "/gpx/rte/rtept", 0UL }, + { tt_rte_rtept_ele, 0, "/gpx/rte/rtept/ele", 0UL }, + { tt_rte_rtept_time, 0, "/gpx/rte/rtept/time", 0UL }, + { tt_rte_rtept_name, 0, "/gpx/rte/rtept/name", 0UL }, + { tt_rte_rtept_cmt, 0, "/gpx/rte/rtept/cmt", 0UL }, + { tt_rte_rtept_desc, 0, "/gpx/rte/rtept/desc", 0UL }, + { tt_rte_rtept_url, 0, "/gpx/rte/rtept/url", 0UL }, + { tt_rte_rtept_urlname, 0, "/gpx/rte/rtept/urlname", 0UL }, + { tt_rte_rtept_sym, 0, "/gpx/rte/rtept/sym", 0UL }, + + { tt_trk, 0, "/gpx/trk", 0UL }, + { tt_trk_name, 0, "/gpx/trk/name", 0UL }, + { tt_trk_desc, 0, "/gpx/trk/desc", 0UL }, + { tt_trk_trkseg, 0, "/gpx/trk/trkseg", 0UL }, + { tt_trk_number, 0, "/gpx/trk/number", 0UL }, + { tt_trk_trkseg_trkpt, 0, "/gpx/trk/trkseg/trkpt", 0UL }, + { tt_trk_trkseg_trkpt_ele, 0, "/gpx/trk/trkseg/trkpt/ele", 0UL }, + { tt_trk_trkseg_trkpt_time, 0, "/gpx/trk/trkseg/trkpt/time", 0UL }, + { tt_trk_trkseg_trkpt_name, 0, "/gpx/trk/trkseg/trkpt/name", 0UL }, + { tt_trk_trkseg_trkpt_cmt, 0, "/gpx/trk/trkseg/trkpt/cmt", 0UL }, + { tt_trk_trkseg_trkpt_desc, 0, "/gpx/trk/trkseg/trkpt/desc", 0UL }, + { tt_trk_trkseg_trkpt_url, 0, "/gpx/trk/trkseg/trkpt/url", 0UL }, + { tt_trk_trkseg_trkpt_urlname, 0, "/gpx/trk/trkseg/trkpt/urlname", 0UL }, + { tt_trk_trkseg_trkpt_sym, 0, "/gpx/trk/trkseg/trkpt/sym", 0UL }, + { tt_trk_trkseg_trkpt_course, 0, "/gpx/trk/trkseg/trkpt/course", 0UL }, + { tt_trk_trkseg_trkpt_speed, 0, "/gpx/trk/trkseg/trkpt/speed", 0UL }, + + /* Common to tracks, routes, and waypts */ + { tt_fix, 0, "/gpx/wpt/fix", 0UL }, + { tt_fix, 0, "/gpx/trk/trkseg/trkpt/fix", 0UL }, + { tt_fix, 0, "/gpx/rte/rtept/fix", 0UL }, + { tt_sat, 0, "/gpx/wpt/sat", 0UL }, + { tt_sat, 0, "/gpx/trk/trkseg/trkpt/sat", 0UL }, + { tt_sat, 0, "/gpx/rte/rtept/sat", 0UL }, + { tt_pdop, 0, "/gpx/wpt/pdop", 0UL }, + { tt_pdop, 0, "/gpx/trk/trkseg/trkpt/pdop", 0UL }, + { tt_pdop, 0, "/gpx/rte/rtept/pdop", 0UL }, + { tt_hdop, 0, "/gpx/wpt/hdop", 0UL }, + { tt_hdop, 0, "/gpx/trk/trkseg/trkpt/hdop", 0UL }, + { tt_hdop, 0, "/gpx/rte/rtept/hdop", 0UL }, + { tt_vdop, 0, "/gpx/wpt/vdop", 0UL }, + { tt_vdop, 0, "/gpx/trk/trkseg/trkpt/vdop", 0UL }, + { tt_vdop, 0, "/gpx/rte/rtept/hdop", 0UL }, + {0, 0, NULL, 0UL} +}; + +static tag_type +get_tag(const char *t, int *passthrough) +{ + tag_mapping *tm; + unsigned long tcrc = get_crc32_s(t); + + for (tm = tag_path_map; tm->tag_type != 0; tm++) { + if ((tcrc == tm->crc) && (0 == strcmp(tm->tag_name, t))) { + *passthrough = tm->tag_passthrough; + return tm->tag_type; + } + } + *passthrough = 1; + return tt_unknown; +} + +static void +prescan_tags(void) +{ + tag_mapping *tm; + for (tm = tag_path_map; tm->tag_type != 0; tm++) { + tm->crc = get_crc32_s(tm->tag_name); + } +} + +static void +tag_gpx(const char **attrv) +{ + const char **avp; + for (avp = &attrv[0]; *avp; avp += 2) { + if (strcmp(avp[0], "version") == 0) { + gpx_version = avp[1]; + } + else if (strcmp(avp[0], "src") == 0) { + gpx_creator = avp[1]; + } + /* + * Our handling of schemaLocation really is weird. + * If we see we have a "normal" GPX 1.1 header, on read, + * flip our default on write to use that and don't append + * it to the rest... + */ + else if (strcmp(avp[0], "xsi:schemaLocation") == 0) { + if (0 == strcmp(avp[1], DEFAULT_XSI_SCHEMA_LOC_11)) { + if (0 == strcmp(xsi_schema_loc, DEFAULT_XSI_SCHEMA_LOC)) + xfree(xsi_schema_loc); + xsi_schema_loc = xstrdup(DEFAULT_XSI_SCHEMA_LOC_11); + continue; + } + if (0 == strstr(xsi_schema_loc, avp[1])) { + xsi_schema_loc = xstrappend(xsi_schema_loc, " "); + xsi_schema_loc = xstrappend(xsi_schema_loc, avp[1]); + } + } + } +} + +static void +tag_wpt(const char **attrv) +{ + const char **avp = &attrv[0]; + + wpt_tmp = waypt_new(); + + cur_tag = NULL; + while (*avp) { + if (strcmp(avp[0], "lat") == 0) { + sscanf(avp[1], "%lf", + &wpt_tmp->latitude); + } + else if (strcmp(avp[0], "lon") == 0) { + sscanf(avp[1], "%lf", + &wpt_tmp->longitude); + } + avp+=2; + } + fs_ptr = &wpt_tmp->fs; +} + +static void +tag_cache_desc(const char ** attrv) +{ + const char **avp; + + cache_descr_is_html = 0; + for (avp = &attrv[0]; *avp; avp+=2) { + if (strcmp(avp[0], "html") == 0) { + if (strcmp(avp[1], "True") == 0) { + cache_descr_is_html = 1; + } + } + } +} + +static void +tag_gs_cache(const char **attrv) +{ + const char **avp; + + for (avp = &attrv[0]; *avp; avp+=2) { + if (strcmp(avp[0], "id") == 0) { + wpt_tmp->gc_data.id = atoi(avp[1]); + } else if (strcmp(avp[0], "available") == 0) { + if (case_ignore_strcmp(avp[1], "True") == 0) { + wpt_tmp->gc_data.is_available = status_true; + } + else if (case_ignore_strcmp(avp[1], "False") == 0) { + wpt_tmp->gc_data.is_available = status_false; + } + } else if (strcmp(avp[0], "archived") == 0) { + if (case_ignore_strcmp(avp[1], "True") == 0) { + wpt_tmp->gc_data.is_archived = status_true; + } + else if (case_ignore_strcmp(avp[1], "False") == 0) { + wpt_tmp->gc_data.is_archived = status_false; + } + } + } +} + +static void +start_something_else(const char *el, const char **attrv) +{ + const char **avp = attrv; + char **avcp = NULL; + int attr_count = 0; + xml_tag *new_tag; + fs_xml *fs_gpx; + + if ( !fs_ptr ) { + return; + } + + new_tag = (xml_tag *)xcalloc(sizeof(xml_tag),1); + new_tag->tagname = xstrdup(el); + + /* count attributes */ + while (*avp) { + attr_count++; + avp++; + } + + /* copy attributes */ + avp = attrv; + new_tag->attributes = (char **)xcalloc(sizeof(char *),attr_count+1); + avcp = new_tag->attributes; + while (*avp) { + *avcp = xstrdup(*avp); + avcp++; + avp++; + } + *avcp = NULL; + + if ( cur_tag ) { + if ( cur_tag->child ) { + cur_tag = cur_tag->child; + while ( cur_tag->sibling ) { + cur_tag = cur_tag->sibling; + } + cur_tag->sibling = new_tag; + new_tag->parent = cur_tag->parent; + } + else { + cur_tag->child = new_tag; + new_tag->parent = cur_tag; + } + } + else { + fs_gpx = (fs_xml *)fs_chain_find( *fs_ptr, FS_GPX ); + + if ( fs_gpx && fs_gpx->tag ) { + cur_tag = fs_gpx->tag; + while ( cur_tag->sibling ) { + cur_tag = cur_tag->sibling; + } + cur_tag->sibling = new_tag; + new_tag->parent = NULL; + } + else { + fs_gpx = fs_xml_alloc(FS_GPX); + fs_gpx->tag = new_tag; + fs_chain_add( fs_ptr, (format_specific_data *)fs_gpx ); + new_tag->parent = NULL; + } + } + cur_tag = new_tag; +} + +static void +end_something_else() +{ + if ( cur_tag ) { + cur_tag = cur_tag->parent; + } +} + +static void +tag_log_wpt(const char **attrv) +{ + waypoint * lwp_tmp; + const char **avp = &attrv[0]; + + /* create a new waypoint */ + lwp_tmp = waypt_new(); + + /* extract the lat/lon attributes */ + while (*avp) { + if (strcmp(avp[0], "lat") == 0) { + sscanf(avp[1], "%lf", + &lwp_tmp->latitude); + } + else if (strcmp(avp[0], "lon") == 0) { + sscanf(avp[1], "%lf", + &lwp_tmp->longitude); + } + avp+=2; + } + /* Make a new shortname. Since this is a groundspeak extension, + we assume that GCBLAH is the current shortname format and that + wpt_tmp refers to the currently parsed waypoint. Unfortunatley, + we need to keep track of log_wpt counts so we don't collide with + dupe shortnames. + */ + + if ((wpt_tmp->shortname) && (strlen(wpt_tmp->shortname) > 2)) { + /* copy of the shortname */ + lwp_tmp->shortname = xcalloc(7, 1); + sprintf(lwp_tmp->shortname, "%-4.4s%02d", + &wpt_tmp->shortname[2], logpoint_ct++); + + waypt_add(lwp_tmp); + } +} + +static void +gpx_start(void *data, const XML_Char *xml_el, const XML_Char **xml_attr) +{ + char *e; + char *ep; + int passthrough; + const char *el = xml_convert_to_char_string(xml_el); + const char **attr = xml_convert_attrs_to_char_string(xml_attr); + + vmem_realloc(¤t_tag, strlen(current_tag.mem) + 2 + strlen(el)); + e = current_tag.mem; + ep = e + strlen(e); + *ep++ = '/'; + strcpy(ep, el); + + + /* + * FIXME: Find out why a cdatastr[0] doesn't adequately reset the + * cdata handler. + */ + memset(cdatastr.mem, 0, cdatastr.size); + + switch (get_tag(current_tag.mem, &passthrough)) { + case tt_gpx: + tag_gpx(attr); + break; + case tt_wpt: + tag_wpt(attr); + break; + case tt_wpt_link: + if (0 == strcmp(attr[0], "href")) { + link_url = attr[1]; + } + break; + case tt_wpt_link_text: + link_text = cdatastr.mem; + break; + case tt_rte: + rte_head = route_head_alloc(); + route_add_head(rte_head); + fs_ptr = &rte_head->fs; + break; + case tt_rte_rtept: + tag_wpt(attr); + break; + case tt_trk: + trk_head = route_head_alloc(); + track_add_head(trk_head); + fs_ptr = &trk_head->fs; + break; + case tt_trk_trkseg_trkpt: + tag_wpt(attr); + break; + case tt_unknown: + start_something_else(el, attr); + return; + case tt_cache: + tag_gs_cache(attr); + break; + case tt_cache_log_wpt: + if (opt_logpoint) + tag_log_wpt(attr); + break; + case tt_cache_desc_long: + case tt_cache_desc_short: + tag_cache_desc(attr); + break; + case tt_cache_placer: + if (*attr && (0 == strcmp(attr[0], "id"))) { + wpt_tmp->gc_data.placer_id = atoi(attr[1]); + } + default: + break; + } + if (passthrough) { + start_something_else(el, attr); + } + xml_free_converted_string(el); + xml_free_converted_attrs(attr); +} + +struct +gs_type_mapping{ + geocache_type type; + const char *name; +} gs_type_map[] = { + { gt_traditional, "Traditional Cache" }, + { gt_traditional, "Traditional" }, /* opencaching.de */ + { gt_multi, "Multi-cache" }, + { gt_multi, "Multi" }, /* opencaching.de */ + { gt_virtual, "Virtual Cache" }, + { gt_virtual, "Virtual" }, /* opencaching.de */ + { gt_event, "Event Cache" }, + { gt_event, "Event" }, /* opencaching.de */ + { gt_webcam, "Webcam Cache" }, + { gt_webcam, "Webcam" }, /* opencaching.de */ + { gt_suprise, "Unknown Cache" }, + { gt_earth, "Earthcache" }, + { gt_earth, "Earth" }, /* opencaching.de */ + { gt_cito, "Cache In Trash Out Event" }, + { gt_letterbox, "Letterbox Hybrid" }, + { gt_locationless, "Locationless (Reverse) Cache" }, + { gt_ape, "Project APE Cache" }, + { gt_mega, "Mega-Event Cache" }, + + { gt_benchmark, "Benchmark" }, /* Not Groundspeak; for GSAK */ +}; + +struct +gs_container_mapping{ + geocache_container type; + const char *name; +} gs_container_map[] = { + { gc_other, "Unknown" }, + { gc_other, "Other" }, /* Synonym on read. */ + { gc_micro, "Micro" }, + { gc_regular, "Regular" }, + { gc_large, "Large" }, + { gc_small, "Small" }, + { gc_virtual, "Virtual" } +}; + +geocache_type +gs_mktype(const char *t) +{ + int i; + int sz = sizeof(gs_type_map) / sizeof(gs_type_map[0]); + + for (i = 0; i < sz; i++) { + if (0 == case_ignore_strcmp(t, gs_type_map[i].name)) { + return gs_type_map[i].type; + } + } + return gt_unknown; +} + +const char * +gs_get_cachetype(geocache_type t) +{ + int i; + int sz = sizeof(gs_type_map) / sizeof(gs_type_map[0]); + + for (i = 0; i < sz; i++) { + if (t == gs_type_map[i].type) { + return gs_type_map[i].name; + } + } + return "Unknown"; +} + +geocache_container +gs_mkcont(const char *t) +{ + int i; + int sz = sizeof(gs_container_map) / sizeof(gs_container_map[0]); + + for (i = 0; i < sz; i++) { + if (0 == case_ignore_strcmp(t, gs_container_map[i].name)) { + return gs_container_map[i].type; + } + } + return gc_unknown; +} + +const char * +gs_get_container(geocache_container t) +{ + int i; + int sz = sizeof(gs_container_map) / sizeof(gs_container_map[0]); + + for (i = 0; i < sz; i++) { + if (t == gs_container_map[i].type) { + return gs_container_map[i].name; + } + } + return "Unknown"; +} + +time_t +xml_parse_time( const char *cdatastr, int *microsecs ) +{ + int off_hr = 0; + int off_min = 0; + int off_sign = 1; + char *offsetstr = NULL; + char *pointstr = NULL; + struct tm tm; + time_t rv = 0; + char *timestr = xstrdup( cdatastr ); + + memset(&tm, 0, sizeof(tm)); + + offsetstr = strchr( timestr, 'Z' ); + if ( offsetstr ) { + /* zulu time; offsets stay at defaults */ + *offsetstr = '\0'; + } else { + offsetstr = strchr( timestr, '+' ); + if ( offsetstr ) { + /* positive offset; parse it */ + *offsetstr = '\0'; + sscanf( offsetstr+1, "%d:%d", &off_hr, &off_min ); + } else { + offsetstr = strchr( timestr, 'T' ); + if ( offsetstr ) { + offsetstr = strchr( offsetstr, '-' ); + if ( offsetstr ) { + /* negative offset; parse it */ + *offsetstr = '\0'; + sscanf( offsetstr+1, "%d:%d", + &off_hr, &off_min ); + off_sign = -1; + } + } + } + } + + pointstr = strchr( timestr, '.' ); + if ( pointstr ) { + if (microsecs) { + double fsec; + sscanf(pointstr, "%le", &fsec); + /* Round to avoid FP jitter */ + *microsecs = .5 + (fsec * 1000000.0) ; + } + *pointstr = '\0'; + } + + sscanf(timestr, "%d-%d-%dT%d:%d:%d", + &tm.tm_year, + &tm.tm_mon, + &tm.tm_mday, + &tm.tm_hour, + &tm.tm_min, + &tm.tm_sec); + tm.tm_mon -= 1; + tm.tm_year -= 1900; + tm.tm_isdst = 0; + + rv = mkgmtime(&tm) - off_sign*off_hr*3600 - off_sign*off_min*60; + + xfree(timestr); + + return rv; +} + +static void +gpx_end(void *data, const XML_Char *xml_el) +{ + const char *el = xml_convert_to_char_string(xml_el); + char *s = strrchr(current_tag.mem, '/'); + float x; + char *cdatastrp = cdatastr.mem; + int passthrough; + static time_t gc_log_date; + tag_type tag; + + if (strcmp(s + 1, el)) { + fprintf(stderr, "Mismatched tag %s\n", el); + } + + tag = get_tag(current_tag.mem, &passthrough); + switch(tag) { + /* + * First, the tags that are file-global. + */ + case tt_name: + gpx_add_to_global(&gpx_global->name, cdatastrp); + break; + case tt_desc: + gpx_add_to_global(&gpx_global->desc, cdatastrp); + break; + case tt_author: + gpx_add_to_global(&gpx_global->author, cdatastrp); + break; + case tt_email: + gpx_add_to_global(&gpx_global->email, cdatastrp); + if (gpx_email == NULL) { + gpx_email = xstrdup(cdatastrp); + } + break; + case tt_url: + gpx_add_to_global(&gpx_global->url, cdatastrp); + break; + case tt_urlname: + gpx_add_to_global(&gpx_global->urlname, cdatastrp); + break; + case tt_keywords: + gpx_add_to_global(&gpx_global->keywords, cdatastrp); + break; + + /* + * Waypoint-specific tags. + */ + case tt_wpt: + waypt_add(wpt_tmp); + logpoint_ct = 0; + cur_tag = NULL; + wpt_tmp = NULL; + break; + case tt_cache_name: + if (wpt_tmp->notes != NULL) xfree(wpt_tmp->notes); + wpt_tmp->notes = xstrdup(cdatastrp); + break; + case tt_cache_container: + wpt_tmp->gc_data.container = gs_mkcont(cdatastrp); + break; + case tt_cache_type: + wpt_tmp->gc_data.type = gs_mktype(cdatastrp); + break; + case tt_cache_difficulty: + sscanf(cdatastrp, "%f", &x); + wpt_tmp->gc_data.diff = x * 10; + break; + case tt_cache_hint: + rtrim(cdatastrp); + if (cdatastrp[0]) { + wpt_tmp->gc_data.hint = xstrdup(cdatastrp); + } + break; + case tt_cache_desc_long: + rtrim(cdatastrp); + if (cdatastrp[0]) { + wpt_tmp->gc_data.desc_long.is_html = cache_descr_is_html; + wpt_tmp->gc_data.desc_long.utfstring = xstrdup(cdatastrp); + } + break; + case tt_cache_desc_short: + rtrim(cdatastrp); + if (cdatastrp[0]) { + wpt_tmp->gc_data.desc_short.is_html = cache_descr_is_html; + wpt_tmp->gc_data.desc_short.utfstring = xstrdup(cdatastrp); + } + break; + case tt_cache_terrain: + sscanf(cdatastrp, "%f", &x); + wpt_tmp->gc_data.terr = x * 10; + break; + case tt_cache_placer: + wpt_tmp->gc_data.placer = xstrdup(cdatastrp); + break; + case tt_cache_log_date: + gc_log_date = xml_parse_time( cdatastrp, NULL ); + break; + /* + * "Found it" logs follow the date according to the schema, + * if this is the first "found it" for this waypt, just use the + * last date we saw in this log. + */ + case tt_cache_log_type: + if ((0 == strcmp(cdatastrp, "Found it")) && + (0 == wpt_tmp->gc_data.last_found)) { + wpt_tmp->gc_data.last_found = gc_log_date; + } + gc_log_date = 0; + break; + + /* + * Garmin-waypoint-specific tags. + */ + case tt_garmin_wpt_proximity: + case tt_garmin_wpt_temperature: + case tt_garmin_wpt_depth: + case tt_garmin_wpt_display_mode: + case tt_garmin_wpt_category: + case tt_garmin_wpt_addr: + case tt_garmin_wpt_city: + case tt_garmin_wpt_state: + case tt_garmin_wpt_country: + case tt_garmin_wpt_postal_code: + case tt_garmin_wpt_phone_nr: + garmin_fs_xml_convert(tt_garmin_wpt_extensions, tag, cdatastrp, wpt_tmp); + break; + + /* + * Route-specific tags. + */ + case tt_rte_name: + rte_head->rte_name = xstrdup(cdatastrp); + break; + case tt_rte: + break; + case tt_rte_rtept: + route_add_wpt(rte_head, wpt_tmp); + wpt_tmp = NULL; + break; + case tt_rte_desc: + rte_head->rte_desc = xstrdup(cdatastrp); + break; + case tt_rte_number: + rte_head->rte_num = atoi(cdatastrp); + break; + /* + * Track-specific tags. + */ + case tt_trk_name: + trk_head->rte_name = xstrdup(cdatastrp); + break; + case tt_trk: + break; + case tt_trk_trkseg_trkpt: + track_add_wpt(trk_head, wpt_tmp); + wpt_tmp = NULL; + break; + case tt_trk_desc: + trk_head->rte_desc = xstrdup(cdatastrp); + break; + case tt_trk_number: + trk_head->rte_num = atoi(cdatastrp); + break; + case tt_trk_trkseg_trkpt_course: + WAYPT_SET(wpt_tmp, course, atof(cdatastrp)); + break; + case tt_trk_trkseg_trkpt_speed: + WAYPT_SET(wpt_tmp, speed, atof(cdatastrp)); + break; + + /* + * Items that are actually in multiple categories. + */ + case tt_wpt_ele: + case tt_rte_rtept_ele: + case tt_trk_trkseg_trkpt_ele: + sscanf(cdatastrp, "%lf", &wpt_tmp->altitude); + break; + case tt_wpt_name: + case tt_rte_rtept_name: + case tt_trk_trkseg_trkpt_name: + wpt_tmp->shortname = xstrdup(cdatastrp); + break; + case tt_wpt_sym: + case tt_rte_rtept_sym: + case tt_trk_trkseg_trkpt_sym: + wpt_tmp->icon_descr = xstrdup(cdatastrp); + wpt_tmp->wpt_flags.icon_descr_is_dynamic = 1; + break; + case tt_wpt_time: + case tt_trk_trkseg_trkpt_time: + case tt_rte_rtept_time: + wpt_tmp->creation_time = xml_parse_time( cdatastrp, &wpt_tmp->microseconds ); + break; + case tt_wpt_cmt: + case tt_rte_rtept_cmt: + case tt_trk_trkseg_trkpt_cmt: + wpt_tmp->description = xstrdup(cdatastrp); + break; + case tt_wpt_desc: + case tt_trk_trkseg_trkpt_desc: + case tt_rte_rtept_desc: + if (wpt_tmp->notes != NULL) xfree(wpt_tmp->notes); + wpt_tmp->notes = xstrdup(cdatastrp); + break; + case tt_pdop: + wpt_tmp->pdop = atof(cdatastrp); + break; + case tt_hdop: + wpt_tmp->hdop = atof(cdatastrp); + break; + case tt_vdop: + wpt_tmp->vdop = atof(cdatastrp); + break; + case tt_sat: + wpt_tmp->sat = atof(cdatastrp); + break; + case tt_fix: + wpt_tmp->fix = atoi(cdatastrp)-1; + if ( wpt_tmp->fix < fix_2d) { + if (!case_ignore_strcmp(cdatastrp, "none")) + wpt_tmp->fix = fix_none; + else if (!case_ignore_strcmp(cdatastrp, "dgps")) + wpt_tmp->fix = fix_dgps; + else if (!case_ignore_strcmp(cdatastrp, "pps")) + wpt_tmp->fix = fix_pps; + else + wpt_tmp->fix = fix_unknown; + } + break; + case tt_wpt_url: + case tt_trk_trkseg_trkpt_url: + case tt_rte_rtept_url: + wpt_tmp->url = xstrdup(cdatastrp); + break; + case tt_wpt_urlname: + case tt_trk_trkseg_trkpt_urlname: + case tt_rte_rtept_urlname: + wpt_tmp->url_link_text = xstrdup(cdatastrp); + break; + case tt_wpt_link: +//TODO: implement GPX 1.1 case tt_trk_trkseg_trkpt_link: +//TODO: implement GPX 1.1 case tt_rte_rtept_link: + { + char *lt = link_text; + if (lt) { + lt = xstrdup(lrtrim(link_text)); + } + + waypt_add_url(wpt_tmp, xstrdup(link_url), lt); + link_text = NULL; + } + break; + case tt_unknown: + end_something_else(); + *s = 0; + return; + default: + break; + } + + if (passthrough) { + end_something_else(); + } + + *s = 0; + xml_free_converted_string(el); +} + +#if ! HAVE_LIBEXPAT +static void +gpx_rd_init(const char *fname) +{ + fatal(MYNAME ": This build excluded GPX support because expat was not installed.\n"); +} + +static void +gpx_rd_deinit(void) +{ +} + +#else /* NO_EXPAT */ + +static void +gpx_cdata(void *dta, const XML_Char *xml_el, int len) +{ + char *estr; + int *cdatalen; + char **cdata; + xml_tag *tmp_tag; + size_t slen = strlen(cdatastr.mem); + const char *s = xml_convert_to_char_string_n(xml_el, &len); + + vmem_realloc(&cdatastr, 1 + len + slen); + estr = ((char *) cdatastr.mem) + slen; + memcpy(estr, s, len); + estr[len] = 0; + + if (!cur_tag) + return; + + if ( cur_tag->child ) { + tmp_tag = cur_tag->child; + while ( tmp_tag->sibling ) { + tmp_tag = tmp_tag->sibling; + } + cdata = &(tmp_tag->parentcdata); + cdatalen = &(tmp_tag->parentcdatalen); + } + else { + cdata = &(cur_tag->cdata); + cdatalen = &(cur_tag->cdatalen); + } + estr = *cdata; + *cdata = xcalloc( *cdatalen + len + 1, 1); + if ( estr ) { + memcpy( *cdata, estr, *cdatalen); + xfree( estr ); + } + estr = *cdata + *cdatalen; + memcpy( estr, s, len ); + *(estr+len) = '\0'; + *cdatalen += len; + + xml_free_converted_string(s); +} + +static void +gpx_rd_init(const char *fname) +{ + if ( fname[0] ) { + fd = gbfopen(fname, "r", MYNAME); + input_fname = fname; + } + else { + fd = NULL; + input_string = fname+1; + input_string_len = strlen(input_string); + input_fname = NULL; + } + + + file_time = 0; + current_tag = vmem_alloc(1, 0); + *((char *)current_tag.mem) = '\0'; + + prescan_tags(); + + psr = XML_ParserCreate(NULL); + if (!psr) { + fatal(MYNAME ": Cannot create XML Parser\n"); + } + XML_SetUnknownEncodingHandler(psr, cet_lib_expat_UnknownEncodingHandler, NULL); + + cdatastr = vmem_alloc(1, 0); + *((char *)cdatastr.mem) = '\0'; + + if (!xsi_schema_loc) { + xsi_schema_loc = xstrdup(DEFAULT_XSI_SCHEMA_LOC); + } + if (!xsi_schema_loc) { + fatal("gpx: Unable to allocate %ld bytes of memory.\n", + (unsigned long) strlen(DEFAULT_XSI_SCHEMA_LOC) + 1); + } + + if (NULL == gpx_global) { + gpx_global = xcalloc(sizeof(*gpx_global), 1); + QUEUE_INIT(&gpx_global->name.queue); + QUEUE_INIT(&gpx_global->desc.queue); + QUEUE_INIT(&gpx_global->author.queue); + QUEUE_INIT(&gpx_global->email.queue); + QUEUE_INIT(&gpx_global->url.queue); + QUEUE_INIT(&gpx_global->urlname.queue); + QUEUE_INIT(&gpx_global->keywords.queue); + } + + XML_SetElementHandler(psr, gpx_start, gpx_end); + XML_SetCharacterDataHandler(psr, gpx_cdata); + fs_ptr = NULL; +} + +static +void +gpx_rd_deinit(void) +{ + vmem_free(¤t_tag); + vmem_free(&cdatastr); + /* + * Don't free schema_loc. It really is important that we preserve + * this across reads or else merges/copies of files with different + * schemas won't retain the headers. + * + * moved to gpx_exit + + if ( xsi_schema_loc ) { + xfree(xsi_schema_loc); + xsi_schema_loc = NULL; + } + */ + + if ( gpx_email ) { + xfree(gpx_email); + gpx_email = NULL; + } + if ( gpx_author ) { + xfree(gpx_author); + gpx_author = NULL; + } + if (fd) { + gbfclose(fd); + } + XML_ParserFree(psr); + psr = NULL; + wpt_tmp = NULL; + cur_tag = NULL; + input_fname = NULL; +} +#endif + +static void +gpx_wr_init(const char *fname) +{ + mkshort_handle = mkshort_new_handle(); + + ofd = gbfopen(fname, "w", MYNAME); +} + +static void +gpx_wr_deinit(void) +{ + gbfclose(ofd); + mkshort_del_handle(&mkshort_handle); +} + +void +gpx_read(void) +{ +#if HAVE_LIBEXPAT + int len; + int done = 0; + char *buf = xmalloc(MY_CBUF_SZ); + int result = 0; + int extra; + + while (!done) { + if ( fd ) { + /* + * The majority of this block (in fact, all but the + * call to XML_Parse) are a disgusting hack to + * correct defective GPX files that Geocaching.com + * issues as pocket queries. They contain escape + * characters as entities (�-) which makes + * them not validate which croaks expat and torments + * users. + * + * Look for '&' in the last maxentlength chars. If + * we find it, strip it, then read byte-at-a-time + * until we find a non-entity. + */ + char *badchar; + char *semi; + int maxentlength = 8; + len = gbfread(buf, 1, MY_CBUF_SZ - maxentlength, fd); + done = gbfeof(fd) || !len; + buf[len] = '\0'; + if (len < maxentlength) { + maxentlength = len; + } + badchar = buf+len-maxentlength; + badchar = strchr( badchar, '&' ); + extra = maxentlength - 1; /* for terminator */ + while ( badchar && len < MY_CBUF_SZ-1) { + semi = strchr( badchar, ';'); + while ( extra && !semi ) { + len += gbfread( buf+len, 1, 1, fd); + buf[len]='\0'; + extra--; + if ( buf[len-1] == ';') + semi= buf+len-1; + } + badchar = strchr( badchar+1, '&' ); + } + { + char *hex="0123456789abcdef"; + badchar = strstr( buf, "&#x" ); + while ( badchar ) { + int val = 0; + char *hexit = badchar+3; + semi = strchr( badchar, ';' ); + if ( semi ) { + while (*hexit && *hexit != ';') { + char hc = isalpha(*hexit) ? tolower (*hexit) : *hexit; + val *= 16; + val += strchr( hex, hc)-hex; + hexit++; + } + + if ( val < 32 ) { + warning( MYNAME ": Ignoring illegal character %s;\n\tConsider emailing %s at <%s>\n\tabout illegal characters in their GPX files.\n", badchar, gpx_author?gpx_author:"(unknown author)", gpx_email?gpx_email:"(unknown email address)" ); + memmove( badchar, semi+1, strlen(semi+1)+1 ); + len -= (semi-badchar)+1; + badchar--; + } + } + badchar = strstr( badchar+1, "&#x" ); + } + } + result = XML_Parse(psr, buf, len, done); + } + else if (input_string) { + done = 0; + result = XML_Parse(psr, input_string, + input_string_len, done ); + done = 1; + } + else { + done = 1; + result = -1; + } + if (!result) { + fatal(MYNAME ": XML parse error at line %d of '%s' : %s\n", + (int) XML_GetCurrentLineNumber(psr), + input_fname ? input_fname : "unknown file", + XML_ErrorString(XML_GetErrorCode(psr))); + } + } + xfree(buf); +#endif /* HAVE_LIBEXPAT */ +} + +static void +fprint_tag_and_attrs( char *prefix, char *suffix, xml_tag *tag ) +{ + char **pa; + gbfprintf( ofd, "%s%s", prefix, tag->tagname ); + pa = tag->attributes; + if ( pa ) { + while ( *pa ) { + gbfprintf( ofd, " %s=\"%s\"", pa[0], pa[1] ); + pa += 2; + } + } + gbfprintf( ofd, "%s", suffix ); +} + +static void +fprint_xml_chain( xml_tag *tag, const waypoint *wpt ) +{ + char *tmp_ent; + while ( tag ) { + if ( !tag->cdata && !tag->child ) { + fprint_tag_and_attrs( "<", " />", tag ); + } + else { + fprint_tag_and_attrs( "<", ">", tag ); + + if ( tag->cdata ) { + tmp_ent = xml_entitize( tag->cdata ); + gbfprintf( ofd, "%s", tmp_ent ); + xfree(tmp_ent); + } + if ( tag->child ) { + fprint_xml_chain(tag->child, wpt); + } + if ( wpt && wpt->gc_data.exported && + strcmp(tag->tagname, "groundspeak:cache" ) == 0 ) { + xml_write_time( ofd, wpt->gc_data.exported, 0, + "groundspeak:exported" ); + } + gbfprintf( ofd, "\n", tag->tagname); + } + if ( tag->parentcdata ) { + tmp_ent = xml_entitize(tag->parentcdata); + gbfprintf(ofd, "%s", tmp_ent ); + xfree(tmp_ent); + } + tag = tag->sibling; + } +} + +void free_gpx_extras( xml_tag *tag ) +{ + xml_tag *next = NULL; + char **ap; + + while ( tag ) { + if (tag->cdata) { + xfree(tag->cdata); + } + if (tag->child) { + free_gpx_extras(tag->child); + } + if (tag->parentcdata) { + xfree(tag->parentcdata); + } + if (tag->tagname) { + xfree(tag->tagname); + } + if (tag->attributes) { + ap = tag->attributes; + + while (*ap) + xfree(*ap++); + + xfree(tag->attributes); + } + + next = tag->sibling; + xfree(tag); + tag = next; + } +} + +/* + * Handle the grossness of GPX 1.0 vs. 1.1 handling of linky links. + */ +static void +write_gpx_url(const waypoint *waypointp) +{ + char *tmp_ent; + + if (waypointp->url == NULL) { + return; + } + + if (gpx_wversion_num > 10) { + url_link *tail; + for (tail = (url_link *)&waypointp->url_next; tail; tail = tail->url_next) { + tmp_ent = xml_entitize(tail->url); + gbfprintf(ofd, " \n", + urlbase ? urlbase : "", tmp_ent); + write_optional_xml_entity(ofd, " ", "text", + tail->url_link_text); + gbfprintf(ofd, " \n"); + xfree(tmp_ent); + } + } else { + tmp_ent = xml_entitize(waypointp->url); + gbfprintf(ofd, " %s%s\n", + urlbase ? urlbase : "", tmp_ent); + write_optional_xml_entity(ofd, " ", "urlname", + waypointp->url_link_text); + xfree(tmp_ent); + } +} + +/* + * Write optional accuracy information for a given (way|track|route)point + * to the output stream. Done in one place since it's common for all three. + * Order counts. + */ +static void +gpx_write_common_acc(const waypoint *waypointp, const char *indent) +{ + char *fix = NULL; + + switch (waypointp->fix) { + case fix_2d: + fix = "2d"; + break; + case fix_3d: + fix = "3d"; + break; + case fix_dgps: + fix = "dgps"; + break; + case fix_pps: + fix = "pps"; + break; + case fix_none: + fix = "none"; + break; + /* GPX spec says omit if we don't know. */ + case fix_unknown: + default: + break; + } + if (fix) { + gbfprintf(ofd, "%s%s\n", indent, fix); + } + if (waypointp->sat > 0) { + gbfprintf(ofd, "%s%d\n", indent, waypointp->sat); + } + if (waypointp->hdop) { + gbfprintf(ofd, "%s%f\n", indent, waypointp->hdop); + } + if (waypointp->vdop) { + gbfprintf(ofd, "%s%f\n", indent, waypointp->vdop); + } + if (waypointp->pdop) { + gbfprintf(ofd, "%s%f\n", indent, waypointp->pdop); + } +} + +static void +gpx_write_common_position(const waypoint *waypointp, const char *indent) +{ + if (waypointp->altitude != unknown_alt) { + gbfprintf(ofd, "%s%f\n", + indent, waypointp->altitude); + } + if (waypointp->creation_time) { + xml_write_time(ofd, waypointp->creation_time, waypointp->microseconds, "time"); + } +} + +static void +gpx_write_common_description(const waypoint *waypointp, const char *indent, + const char *oname) +{ + write_optional_xml_entity(ofd, indent, "name", oname); + write_optional_xml_entity(ofd, indent, "cmt", waypointp->description); + if (waypointp->notes && waypointp->notes[0]) + write_xml_entity(ofd, indent, "desc", waypointp->notes); + else + write_optional_xml_entity(ofd, indent, "desc", waypointp->description); + write_gpx_url(waypointp); + write_optional_xml_entity(ofd, indent , "sym", waypointp->icon_descr); +} + +static void +gpx_waypt_pr(const waypoint *waypointp) +{ + const char *oname; + char *odesc; + fs_xml *fs_gpx; + garmin_fs_t *gmsd; /* gARmIN sPECIAL dATA */ + + /* + * Desparation time, try very hard to get a good shortname + */ + odesc = waypointp->notes; + if (!odesc) { + odesc = waypointp->description; + } + if (!odesc) { + odesc = waypointp->shortname; + } + + oname = global_opts.synthesize_shortnames ? + mkshort(mkshort_handle, odesc) : + waypointp->shortname; + + gbfprintf(ofd, "\n", + waypointp->latitude, + waypointp->longitude); + + gpx_write_common_position(waypointp, " "); + gpx_write_common_description(waypointp, " ", oname); + gpx_write_common_acc(waypointp, " "); + + fs_gpx = (fs_xml *)fs_chain_find( waypointp->fs, FS_GPX ); + gmsd = GMSD_FIND(waypointp); + if ( fs_gpx ) { + if (! gmsd) fprint_xml_chain( fs_gpx->tag, waypointp ); + } + if (gmsd && (gpx_wversion_num > 10)) { + /* MapSource doesn't accepts extensions from 1.0 */ + garmin_fs_xml_fprint(ofd, waypointp); + } + gbfprintf(ofd, "\n"); +} + +static void +gpx_track_hdr(const route_head *rte) +{ + fs_xml *fs_gpx; + + gbfprintf(ofd, "\n"); + write_optional_xml_entity(ofd, " ", "name", rte->rte_name); + write_optional_xml_entity(ofd, " ", "desc", rte->rte_desc); + if (rte->rte_num) { + gbfprintf(ofd, "%d\n", rte->rte_num); + } + gbfprintf(ofd, "\n"); + + fs_gpx = (fs_xml *)fs_chain_find( rte->fs, FS_GPX ); + if ( fs_gpx ) { + fprint_xml_chain( fs_gpx->tag, NULL ); + } +} + +static void +gpx_track_disp(const waypoint *waypointp) +{ + fs_xml *fs_gpx; + + gbfprintf(ofd, "\n", + waypointp->latitude, + waypointp->longitude); + + gpx_write_common_position(waypointp, " "); + + /* These were accidentally removed from 1.1 */ + if (gpx_wversion_num == 10) { + if WAYPT_HAS(waypointp, course) { + gbfprintf(ofd, " %f\n", + waypointp->course); + } + if WAYPT_HAS(waypointp, speed) { + gbfprintf(ofd, " %f\n", + waypointp->speed); + } + } + + /* GPX doesn't require a name on output, so if we made one up + * on input, we might as well say nothing. + */ + gpx_write_common_description(waypointp, " ", + waypointp->wpt_flags.shortname_is_synthetic ? + NULL : waypointp->shortname); + gpx_write_common_acc(waypointp, " "); + + fs_gpx = (fs_xml *)fs_chain_find( waypointp->fs, FS_GPX ); + if ( fs_gpx ) { + fprint_xml_chain( fs_gpx->tag, waypointp ); + } + + gbfprintf(ofd, "\n"); +} + +static void +gpx_track_tlr(const route_head *rte) +{ + gbfprintf(ofd, "\n"); + gbfprintf(ofd, "\n"); +} + +static +void gpx_track_pr() +{ + track_disp_all(gpx_track_hdr, gpx_track_tlr, gpx_track_disp); +} + +static void +gpx_route_hdr(const route_head *rte) +{ + fs_xml *fs_gpx; + + gbfprintf(ofd, "\n"); + write_optional_xml_entity(ofd, " ", "name", rte->rte_name); + write_optional_xml_entity(ofd, " ", "desc", rte->rte_desc); + if (rte->rte_num) { + gbfprintf(ofd, " %d\n", rte->rte_num); + } + + fs_gpx = (fs_xml *)fs_chain_find( rte->fs, FS_GPX ); + if ( fs_gpx ) { + fprint_xml_chain( fs_gpx->tag, NULL ); + } +} + +static void +gpx_route_disp(const waypoint *waypointp) +{ + fs_xml *fs_gpx; + + gbfprintf(ofd, " \n", + waypointp->latitude, + waypointp->longitude); + + gpx_write_common_position(waypointp, " "); + gpx_write_common_description(waypointp, " ", waypointp->shortname); + gpx_write_common_acc(waypointp, " "); + + fs_gpx = (fs_xml *)fs_chain_find( waypointp->fs, FS_GPX ); + if ( fs_gpx ) { + fprint_xml_chain( fs_gpx->tag, waypointp ); + } + + gbfprintf(ofd, " \n"); +} + +static void +gpx_route_tlr(const route_head *rte) +{ + gbfprintf(ofd, "\n"); +} + +static +void gpx_route_pr() +{ + /* output routes */ + route_disp_all(gpx_route_hdr, gpx_route_tlr, gpx_route_disp); +} + +static void +gpx_waypt_bound_calc(const waypoint *waypointp) +{ + waypt_add_to_bounds(&all_bounds, waypointp); +} + +static void +gpx_write_bounds(void) +{ + waypt_init_bounds(&all_bounds); + + waypt_disp_all(gpx_waypt_bound_calc); + route_disp_all(NULL, NULL, gpx_waypt_bound_calc); + track_disp_all(NULL, NULL, gpx_waypt_bound_calc); + + if (waypt_bounds_valid(&all_bounds)) { + gbfprintf(ofd, "\n", + all_bounds.min_lat, all_bounds.min_lon, + all_bounds.max_lat, all_bounds.max_lon); + } +} + +static void +gpx_write(void) +{ + time_t now = 0; + int short_length; + + gpx_wversion_num = strtod(gpx_wversion, NULL) * 10; + + if (gpx_wversion_num <= 0) { + fatal(MYNAME ": gpx version number of '%s' not valid.\n", gpx_wversion); + } + + now = current_time(); + + short_length = atoi(snlen); + + if (suppresswhite) { + setshort_whitespace_ok(mkshort_handle, 0); + } + + setshort_length(mkshort_handle, short_length); + + gbfprintf(ofd, "\n", global_opts.charset_name); + gbfprintf(ofd, "\n", xsi_schema_loc); + } else { + gbfprintf(ofd, + "xsi:schemaLocation=" DEFAULT_XSI_SCHEMA_LOC_FMT">\n", + gpx_wversion[0], gpx_wversion[2], + gpx_wversion[0], gpx_wversion[2]); + } + + if (gpx_wversion_num > 10) { + gbfprintf(ofd, "\n"); + } + gpx_write_gdata(&gpx_global->name, "name"); + gpx_write_gdata(&gpx_global->desc, "desc"); + /* In GPX 1.1, author changed from a string to a PersonType. + * since it's optional, we just drop it instead of rewriting it. + */ + if (gpx_wversion_num < 11) { + gpx_write_gdata(&gpx_global->author, "author"); + } + gpx_write_gdata(&gpx_global->email, "email"); + gpx_write_gdata(&gpx_global->url, "url"); + gpx_write_gdata(&gpx_global->urlname, "urlname"); + xml_write_time( ofd, now, 0, "time" ); + gpx_write_gdata(&gpx_global->keywords, "keywords"); + + gpx_write_bounds(); + + if (gpx_wversion_num > 10) { + gbfprintf(ofd, "\n"); + } + + waypt_disp_all(gpx_waypt_pr); + gpx_route_pr(); + gpx_track_pr(); + + gbfprintf(ofd, "\n"); +} + + +static void +gpx_free_gpx_global(void) +{ + gpx_rm_from_global(&gpx_global->name); + gpx_rm_from_global(&gpx_global->desc); + gpx_rm_from_global(&gpx_global->author); + gpx_rm_from_global(&gpx_global->email); + gpx_rm_from_global(&gpx_global->url); + gpx_rm_from_global(&gpx_global->urlname); + gpx_rm_from_global(&gpx_global->keywords); + xfree(gpx_global); +} + +static void +gpx_exit(void) +{ + if ( xsi_schema_loc ) { + xfree(xsi_schema_loc); + xsi_schema_loc = NULL; + } + + if (gpx_global) { + gpx_free_gpx_global(); + gpx_global = NULL; + } +} + +static +arglist_t gpx_args[] = { + { "snlen", &snlen, "Length of generated shortnames", + "32", ARGTYPE_INT, "1", NULL }, + { "suppresswhite", &suppresswhite, + "No whitespace in generated shortnames", + NULL, ARGTYPE_BOOL, ARG_NOMINMAX }, + { "logpoint", &opt_logpoint, + "Create waypoints from geocache log entries", + NULL, ARGTYPE_BOOL, ARG_NOMINMAX }, + { "urlbase", &urlbase, "Base URL for link tag in output", + NULL, ARGTYPE_STRING, ARG_NOMINMAX}, + { "gpxver", &gpx_wversion, "Target GPX version for output", + "1.0", ARGTYPE_STRING, ARG_NOMINMAX}, + ARG_TERMINATOR +}; + +ff_vecs_t gpx_vecs = { + ff_type_file, + FF_CAP_RW_ALL, + gpx_rd_init, + gpx_wr_init, + gpx_rd_deinit, + gpx_wr_deinit, + gpx_read, + gpx_write, + gpx_exit, + gpx_args, + CET_CHARSET_UTF8, 0 /* non-fixed to create non UTF-8 XML's for testing | CET-REVIEW */ +}; +#endif diff --git a/gpsbabel/jeeps/gpslibusb.c b/gpsbabel/jeeps/gpslibusb.c index efc84db73..a629e4bf9 100644 --- a/gpsbabel/jeeps/gpslibusb.c +++ b/gpsbabel/jeeps/gpslibusb.c @@ -28,6 +28,7 @@ #include "gps.h" #include "garminusb.h" #include "gpsusbcommon.h" +#include "garmin_device_xml.h" #define GARMIN_VID 0x91e @@ -57,7 +58,8 @@ static int gusb_bulk_in_ep; static gusb_llops_t libusb_llops; static usb_dev_handle *udev; -static void garmin_usb_scan(libusb_unit_data *, int); +static int garmin_usb_scan(libusb_unit_data *, int); +static const gdx_info *gdx; static int gusb_libusb_send(const garmin_usb_packet *opkt, size_t sz) @@ -277,7 +279,7 @@ garmin_usb_start(struct usb_device *dev) } static -void garmin_usb_scan(libusb_unit_data *lud, int req_unit_number) +int garmin_usb_scan(libusb_unit_data *lud, int req_unit_number) { int found_devices = 0; struct usb_bus *bus; @@ -315,9 +317,21 @@ void garmin_usb_scan(libusb_unit_data *lud, int req_unit_number) gusb_list_units(); exit (0); } + if (0 == found_devices) { + /* It's time for Plan B. The user told us to use + * Garmin Protocol in device "usb:" but it's possible + * that they're talking to one of the dozens of models + * that is wants to read and write GPX files on a + * mounted drive. Try that now. + */ + gdx = gdx_find_file(".", NULL); + if (gdx) return 1; + /* Plan C. */ fatal("Found no Garmin USB devices.\n"); - } + } else { + return 1; + } } static gusb_llops_t libusb_llops = { @@ -350,9 +364,7 @@ gusb_init(const char *portname, gpsdevh **dh) usb_find_busses(); usb_find_devices(); lud->busses = usb_get_busses(); - garmin_usb_scan(lud, req_unit_number); - - return 1; + return garmin_usb_scan(lud, req_unit_number); } #endif /* HAVE_LIBUSB */ -- 2.30.2